Skip to content

feat(i2c): Improve I2C reliability, DMA throughput, and board support#42

Closed
gqsdjhh wants to merge 7 commits intoAlliance-Algorithm:mainfrom
gqsdjhh:i2c
Closed

feat(i2c): Improve I2C reliability, DMA throughput, and board support#42
gqsdjhh wants to merge 7 commits intoAlliance-Algorithm:mainfrom
gqsdjhh:i2c

Conversation

@gqsdjhh
Copy link
Copy Markdown

@gqsdjhh gqsdjhh commented Apr 11, 2026

This PR improves end-to-end I2C behavior across host, c_board, and rmcs_board.

The main goals are:

  • stop silent host-side I2C transmission failures
  • reduce gaps between queued I2C DMA requests on c_board
  • lower c_board I2C RAM cost for burst workloads such as OLED updates
  • restore the hpm5300evk board definitions required by rmcs_board bootloader builds

Changes

Host

  • Treat I2C kBadAlloc as a real failure instead of logging and returning success.
  • Keep the previous behavior for non-I2C packet writes.
  • Add stricter I2C input validation in host-side board APIs.

c_board

  • Rework I2C transfers to use queued DMA requests.
  • Start the next queued I2C request directly from completion/error paths instead of waiting for the next
    main-loop update.
  • Add timeout recovery for stuck I2C DMA transfers.
  • Redesign the I2C queue from request + 511-byte payload buffer to request headers + shared payload chunk pool + DMA staging buffer.
  • Lower I2C-related interrupt priority to (1, 0) to reduce USB contention.
  • Clarify that logical I2C0 maps to physical STM32 I2C2.

rmcs_board

  • Add I2C error reporting for invalid write payload length and invalid read length.
  • Update hpm_sdk submodule from 132349c9 to a82a5354 to restore hpm5300evk board definitions,
    including flash/XPI NOR macros and board_init_gpio_pins().

Why

Previously, large I2C writes could fail on the host side when the protocol batch buffer was exhausted, but
the API still reported success. That made OLED-style transfers appear successful while silently dropping
part of the request.

On c_board, queued I2C DMA requests also had unnecessary idle gaps because the next request was not
started until the main loop reached i2c0->update() again. In addition, the old queue layout scaled poorly
in RAM because each queued request reserved a full payload buffer.

The hpm_sdk submodule update is included because the older submodule state removed hpm5300evk board
definitions required by the current rmcs_board bootloader code.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 11, 2026

Walkthrough

该拉取请求为 librmcs 项目添加了完整的 I2C(Inter-Integrated Circuit)总线支持,涵盖协议定义、固件驱动、中断处理、应用集成和主机 API 多个层级,支持读写操作、可选寄存器寻址、DMA 传输和错误恢复。

Changes

Cohort / File(s) Summary
Core Protocol & Data Structures
core/include/librmcs/data/datas.hpp, core/src/protocol/protocol.hpp
Added DataId::kI2c0 enumerator, new I2cDataView and I2cReadConfigView structs with slave address、payload、register 字段;引入 I2cHeader 位字段结构定义 payload 类型(Write/ReadRequest/ReadResult/Error)。
Core Deserialization
core/src/protocol/deserializer.hpp, core/src/protocol/deserializer.cpp
新增 DeserializeCallback 接口方法处理 I2C 写入、读配置、读结果和错误事件;实现 Deserializer::process_i2c_field() 协程解析 I2C 头部、验证从地址、处理可选寄存器字段、分支 payload 类型。
Core Serialization
core/src/protocol/serializer.hpp
新增 write_i2c_write()write_i2c_read_config()write_i2c_read_result()write_i2c_error() 方法;实现 I2C 协议缓冲区构造、头部设置、payload 复制和验证逻辑。
C-Board 固件驱动
firmware/c_board/app/src/i2c/i2c.hpp, firmware/c_board/app/src/i2c/i2c.cpp, firmware/c_board/app/src/app.cpp
新增 I2c 类实现中断安全的环形缓冲队列 I2C 传输引擎,支持 DMA 分块、超时恢复和错误序列化;初始化 I2C0 实例并集成到主应用循环。
RMCS-Board 固件驱动
firmware/rmcs_board/app/src/i2c/i2c.hpp, firmware/rmcs_board/app/src/app.cpp
新增 I2c 类支持同步读写操作,验证 payload 长度和 7-bit 从地址范围;通过 USB 序列化器返回读结果。
固件 USB 集成
firmware/c_board/app/src/usb/vendor.hpp, firmware/rmcs_board/app/src/usb/vendor.hpp
在 USB vendor deserialization 回调中实现 I2C 处理,将 FieldId::kI2c0 路由到对应 I2C 驱动实例方法。
STM32 HAL 初始化
firmware/c_board/bsp/cubemx/Core/Inc/i2c.h, firmware/c_board/bsp/cubemx/Core/Src/i2c.c, firmware/c_board/bsp/cubemx/Core/Inc/stm32f4xx_hal_conf.h
新增 I2C2 HAL 初始化代码;配置 PF0/PF1 开漏输出、100kHz 时钟、DMA 通道 Stream2(RX)和 Stream7(TX)。
STM32 中断处理
firmware/c_board/bsp/cubemx/Core/Inc/stm32f4xx_it.h, firmware/c_board/bsp/cubemx/Core/Src/stm32f4xx_it.c
新增 I2C2 事件/错误中断处理程序 I2C2_EV_IRQHandler()I2C2_ER_IRQHandler();新增 DMA 流 1、2、7 的中断处理程序。
STM32 板级配置
firmware/c_board/bsp/cubemx/Core/Src/gpio.c, firmware/c_board/bsp/cubemx/Core/Src/dma.c, firmware/c_board/bsp/cubemx/cmake/stm32cubemx/CMakeLists.txt
启用 GPIOF 时钟;配置 DMA1 Stream2/Stream7 的 NVIC 优先级和中断;添加 HAL I2C 驱动源文件 stm32f4xx_hal_i2c.cstm32f4xx_hal_i2c_ex.c
CubeMX 工程配置
firmware/c_board/bsp/cubemx/rmcs_slave.ioc
在 CubeMX .ioc 文件中配置 I2C2 外设、DMA 请求、NVIC 中断、GPIO 引脚 PF0/PF1 映射。
主机端代理 API
host/include/librmcs/agent/c_board.hpp, host/include/librmcs/agent/rmcs_board.hpp
新增 PacketBuilder::i2c0_write()i2c0_read() 方法;添加 DataCallback I2C 接收和错误虚函数回调;验证 slave 地址 0x00–0x7F 范围和 payload 长度上限。
主机端协议处理
host/include/librmcs/protocol/handler.hpp, host/src/protocol/handler.cpp
新增 PacketBuilder::write_i2c()write_i2c_read_config()write_i2c_read_result() 方法;实现 I2C 反序列化回调处理读结果和错误事件;引入 process_result_strict() 用于严格的 I2C 序列化结果验证。

Sequence Diagram(s)

sequenceDiagram
    participant Host as Host Agent
    participant Serializer as Serializer<br/>(Protocol)
    participant Deserializer as Deserializer<br/>(Protocol)
    participant FW as Firmware I2C<br/>Driver
    participant HAL as STM32 HAL<br/>& DMA
    participant Device as I2C<br/>Device

    Host->>Serializer: write_i2c_write(kI2c0, I2cDataView)
    Serializer->>Serializer: Validate slave_address,<br/>encode I2cHeader
    Serializer-->>Host: SerializeResult::kSuccess

    Host->>Deserializer: Feed serialized bytes
    Deserializer->>Deserializer: process_i2c_field()<br/>peek/validate header
    Deserializer->>FW: i2c_write_deserialized_callback<br/>(kI2c0, I2cDataView)
    
    FW->>FW: Enqueue write request<br/>into ring buffer
    FW->>HAL: Start DMA write transfer<br/>via HAL_I2C_Master_Transmit_DMA
    
    HAL->>Device: Send slave address<br/>+ payload bytes
    Device-->>HAL: ACK

    HAL->>FW: tx_complete_callback()
    FW->>FW: Dequeue request,<br/>release payload chunks
    FW->>FW: Process next queued<br/>request or idle
Loading
sequenceDiagram
    participant Host as Host Agent
    participant Serializer as Serializer<br/>(Protocol)
    participant Deserializer as Deserializer<br/>(Protocol)
    participant FW as Firmware I2C<br/>Driver
    participant HAL as STM32 HAL<br/>& DMA
    participant Device as I2C<br/>Device

    Host->>Serializer: write_i2c_read_config<br/>(kI2c0, I2cReadConfigView)
    Serializer->>Serializer: Validate read_length,<br/>encode I2cHeader
    Serializer-->>Host: SerializeResult::kSuccess

    Host->>Deserializer: Feed serialized bytes
    Deserializer->>Deserializer: process_i2c_field()<br/>peek header, branch on<br/>kReadRequest
    Deserializer->>FW: i2c_read_config_<br/>deserialized_callback
    
    FW->>FW: Enqueue read request<br/>into ring buffer
    FW->>HAL: Start DMA read transfer<br/>via HAL_I2C_Master_Receive_DMA
    
    HAL->>Device: Request slave address<br/>+ read N bytes
    Device-->>HAL: Send data bytes

    HAL->>FW: rx_complete_callback()
    FW->>FW: Copy DMA buffer<br/>to read_result
    FW->>Serializer: write_i2c_read_result<br/>(kI2c0, payload)
    Serializer-->>Host: kSuccess
    Host->>Host: Receive I2C data<br/>via i2c_receive_callback
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 嗨呀,I2C 来敲门,
总线轻声道晚安,
DMA 流舞动字节梦,
中断响起交握手,
从地址到数据流,
设备们齐声欢呼~

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.61% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题清晰准确地反映了拉取请求的主要目标和改进方向,涵盖I2C可靠性、DMA吞吐量和多个板卡的支持增强。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (3)
host/include/librmcs/agent/rmcs_board.hpp (1)

151-163: 这里的重写函数请显式加上 override

final 只能限制继续覆写,不能替代 override。这里最好和仓库其它 host 头文件保持同一套约定。

建议修改
-    bool i2c_receive_callback(data::DataId id, const data::I2cDataView& data) final {
+    bool i2c_receive_callback(data::DataId id, const data::I2cDataView& data) override final {
         switch (id) {
         case data::DataId::kI2c0: i2c0_receive_callback(data); return true;
         default: return false;
         }
     }

-    void i2c_error_callback(data::DataId id, uint8_t slave_address) final {
+    void i2c_error_callback(data::DataId id, uint8_t slave_address) override final {
         switch (id) {
         case data::DataId::kI2c0: i2c0_error_callback(slave_address); break;
         default: break;
         }
     }

Based on learnings: In the librmcs codebase, when overriding virtual functions from a base class in C++, declare overrides with the override specifier and do not repeat virtual on the overriding function.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@host/include/librmcs/agent/rmcs_board.hpp` around lines 151 - 163, Add the
override specifier to the two overridden member functions so they follow the
project's convention (use both override and final). Specifically update the
declarations for i2c_receive_callback(data::DataId id, const data::I2cDataView&
data) and i2c_error_callback(data::DataId id, uint8_t slave_address) to include
override (in addition to final) while keeping their current logic that
dispatches to i2c0_receive_callback and i2c0_error_callback for
data::DataId::kI2c0.
host/include/librmcs/agent/c_board.hpp (1)

144-156: 这里的重写函数请显式加上 override

final 不能替代 override;当前写法和仓库里约定的重写风格不一致,也更容易漏掉签名漂移。

建议修改
-    bool i2c_receive_callback(data::DataId id, const data::I2cDataView& data) final {
+    bool i2c_receive_callback(data::DataId id, const data::I2cDataView& data) override final {
         switch (id) {
         case data::DataId::kI2c0: i2c0_receive_callback(data); return true;
         default: return false;
         }
     }

-    void i2c_error_callback(data::DataId id, uint8_t slave_address) final {
+    void i2c_error_callback(data::DataId id, uint8_t slave_address) override final {
         switch (id) {
         case data::DataId::kI2c0: i2c0_error_callback(slave_address); break;
         default: break;
         }
     }

Based on learnings: In the librmcs codebase, when overriding virtual functions from a base class in C++, declare overrides with the override specifier and do not repeat virtual on the overriding function.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@host/include/librmcs/agent/c_board.hpp` around lines 144 - 156, Add the
explicit override specifier to the two overridden methods so they follow the
repo's override style: update i2c_receive_callback(data::DataId, const
data::I2cDataView&) and i2c_error_callback(data::DataId, uint8_t) to include
override (keeping final if desired) instead of relying on final alone; do not
add the virtual keyword.
core/src/protocol/serializer.hpp (1)

563-586: 建议在 required_i2c_size 内补充 payload 语义级约束,避免未来误用生成非法帧。

当前仅校验了长度上限和 payload 枚举值。建议进一步约束:

  • kError 必须 data_len == 0 && has_register == false
  • kReadRequest 必须 payload_bytes == 0(已隐式满足),并可显式保留

这样可把协议合法性收敛到单点校验,减少后续调用方误用风险。

♻️ 建议修改
 static std::size_t required_i2c_size(
     FieldId field_id, I2cHeader::PayloadEnum payload, std::size_t data_len,
     bool has_register) noexcept {
     LIBRMCS_VERIFY_LIKELY(is_i2c_field_id(field_id), 0);
     LIBRMCS_VERIFY_LIKELY(data_len <= ((1U << 9) - 1U), 0);
     switch (payload) {
     case I2cHeader::PayloadEnum::kWrite:
     case I2cHeader::PayloadEnum::kReadRequest:
     case I2cHeader::PayloadEnum::kReadResult:
     case I2cHeader::PayloadEnum::kError: break;
     default: return 0;
     }
+    if (payload == I2cHeader::PayloadEnum::kError) {
+        LIBRMCS_VERIFY_LIKELY(!has_register && data_len == 0, 0);
+    }

     const std::size_t field_header_bytes = required_field_header_size(field_id);
     const std::size_t i2c_header_bytes = sizeof(I2cHeader);
     const std::size_t register_bytes = has_register ? 1 : 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/protocol/serializer.hpp` around lines 563 - 586, In
required_i2c_size, add semantic checks for payload-specific constraints: verify
that when payload == I2cHeader::PayloadEnum::kError then data_len == 0 and
has_register == false, and explicitly assert that payload ==
I2cHeader::PayloadEnum::kReadRequest implies payload_bytes == 0 (or equivalently
data_len == 0) to make the contract explicit; implement these as
LIBRMCS_VERIFY_LIKELY checks (matching existing style) near the existing payload
switch so invalid combinations return 0, and reference the function
required_i2c_size and the enum values I2cHeader::PayloadEnum::kError and
kReadRequest when adding the checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/src/protocol/deserializer.cpp`:
- Around line 363-369: The kError branch in I2cHeader::PayloadEnum::kError
currently returns without consuming optional register or payload bytes, causing
stream desynchronization; update the branch in deserializer.cpp to inspect the
header's has_register and data_length fields and consume/skip the corresponding
bytes from the input buffer/stream (e.g., read and discard the register byte if
has_register is true and advance by data_length bytes for payload) before
constructing data::I2cDataView (with empty payload) and invoking
callback_.i2c_error_deserialized_callback(field_id, data_view); if you already
have a helper like consumeBytes/advanceCursor or existing read routines, reuse
them to avoid duplicating parsing logic.
- Line 307: I2C 分支没有对 data_length(从 header.get<I2cHeader::DataLength>()
读取)做上界校验就传给 peek_bytes,可能导致超大长度把错误处理推迟并引发边界风险;请在使用 peek_bytes 或后续缓冲访问前参考 UART
分支的做法,对 data_length 做上界限制(例如 clamp 到内部缓冲容量或最大允许帧长),并在长度异常时直接返回/报错;修改点包含使用
header.get<I2cHeader::DataLength>() 的位置、传入 peek_bytes
的调用处以及与之对应的错误路径(同样修复类似位置的另两处出现处)。

In `@firmware/c_board/app/src/usb/vendor.hpp`:
- Around line 148-160: 这两个回调 i2c_read_result_deserialized_callback 和
i2c_error_deserialized_callback 不应在接收到协议级错误时调用
core::utility::assert_failed_always()
导致硬故障;请移除或替换断言,在处理不可恢复数据时改为记录/丢弃输入并安全返回(例如调用适当的日志函数或统计错误计数器,并直接
return),确保不会中断整个固件流程;保留函数签名和任何必需的 no-op 行为以满足接口契约。

In `@firmware/rmcs_board/app/src/i2c/i2c.hpp`:
- Around line 33-35: 当前实现对 data.payload.empty() 直接 return
导致空写入被静默忽略,应改为上报非法长度错误并让调用方感知失败;在处理写请求的函数(检查 data.payload.empty() 的位置)移除裸
return,改为通过项目现有的 I2C 错误上报/返回机制上报一个“非法长度”错误(例如调用错误上报函数或返回错误码/false、或设置 I2C
错误状态),确保 payload.empty() 情况不会被当作成功处理。
- Around line 41-51: The code currently const_casts data.payload into a mutable
pointer and passes it to HPM APIs (i2c_master_write and
i2c_master_address_write), which can mutate the buffer; instead allocate a local
mutable buffer, copy data.payload into it, and pass that mutable buffer to
i2c_master_write/i2c_master_address_write (use payload/local_buffer and ensure
size uses data.payload.size()); update both branches (the reg_address branch
using i2c_master_address_write and the plain write branch using
i2c_master_write) to use the temporary writable buffer and free or let it go out
of scope after the call.

In `@firmware/rmcs_board/app/src/usb/vendor.hpp`:
- Around line 158-170: Replace the unconditional hard assert in
i2c_read_result_deserialized_callback and i2c_error_deserialized_callback with
non-fatal handling: detect that the packet direction is incorrect, drop the
message, emit a soft error/reporting call (e.g., increment an error counter,
call a logger or core::diagnostics::report_error) and optionally use a
debug-only assert or log for development builds; ensure both functions stop
calling core::utility::assert_failed_always() and instead record the error and
return gracefully so the device remains usable.

---

Nitpick comments:
In `@core/src/protocol/serializer.hpp`:
- Around line 563-586: In required_i2c_size, add semantic checks for
payload-specific constraints: verify that when payload ==
I2cHeader::PayloadEnum::kError then data_len == 0 and has_register == false, and
explicitly assert that payload == I2cHeader::PayloadEnum::kReadRequest implies
payload_bytes == 0 (or equivalently data_len == 0) to make the contract
explicit; implement these as LIBRMCS_VERIFY_LIKELY checks (matching existing
style) near the existing payload switch so invalid combinations return 0, and
reference the function required_i2c_size and the enum values
I2cHeader::PayloadEnum::kError and kReadRequest when adding the checks.

In `@host/include/librmcs/agent/c_board.hpp`:
- Around line 144-156: Add the explicit override specifier to the two overridden
methods so they follow the repo's override style: update
i2c_receive_callback(data::DataId, const data::I2cDataView&) and
i2c_error_callback(data::DataId, uint8_t) to include override (keeping final if
desired) instead of relying on final alone; do not add the virtual keyword.

In `@host/include/librmcs/agent/rmcs_board.hpp`:
- Around line 151-163: Add the override specifier to the two overridden member
functions so they follow the project's convention (use both override and final).
Specifically update the declarations for i2c_receive_callback(data::DataId id,
const data::I2cDataView& data) and i2c_error_callback(data::DataId id, uint8_t
slave_address) to include override (in addition to final) while keeping their
current logic that dispatches to i2c0_receive_callback and i2c0_error_callback
for data::DataId::kI2c0.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 278cff01-558b-4ce2-a2ae-7ecc08f0e769

📥 Commits

Reviewing files that changed from the base of the PR and between e9675db and 13b60a8.

📒 Files selected for processing (27)
  • .codex
  • core/include/librmcs/data/datas.hpp
  • core/src/protocol/deserializer.cpp
  • core/src/protocol/deserializer.hpp
  • core/src/protocol/protocol.hpp
  • core/src/protocol/serializer.hpp
  • firmware/c_board/app/src/app.cpp
  • firmware/c_board/app/src/i2c/i2c.cpp
  • firmware/c_board/app/src/i2c/i2c.hpp
  • firmware/c_board/app/src/usb/vendor.hpp
  • firmware/c_board/bsp/cubemx/Core/Inc/i2c.h
  • firmware/c_board/bsp/cubemx/Core/Inc/stm32f4xx_hal_conf.h
  • firmware/c_board/bsp/cubemx/Core/Inc/stm32f4xx_it.h
  • firmware/c_board/bsp/cubemx/Core/Src/dma.c
  • firmware/c_board/bsp/cubemx/Core/Src/gpio.c
  • firmware/c_board/bsp/cubemx/Core/Src/i2c.c
  • firmware/c_board/bsp/cubemx/Core/Src/main.c
  • firmware/c_board/bsp/cubemx/Core/Src/stm32f4xx_it.c
  • firmware/c_board/bsp/cubemx/cmake/stm32cubemx/CMakeLists.txt
  • firmware/c_board/bsp/cubemx/rmcs_slave.ioc
  • firmware/rmcs_board/app/src/app.cpp
  • firmware/rmcs_board/app/src/i2c/i2c.hpp
  • firmware/rmcs_board/app/src/usb/vendor.hpp
  • host/include/librmcs/agent/c_board.hpp
  • host/include/librmcs/agent/rmcs_board.hpp
  • host/include/librmcs/protocol/handler.hpp
  • host/src/protocol/handler.cpp

payload_type = header.get<I2cHeader::PayloadType>();
has_register = header.get<I2cHeader::HasRegister>();
slave_address = header.get<I2cHeader::SlaveAddress>();
data_length = header.get<I2cHeader::DataLength>();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

缺少 I2C data_length 上界校验。

data_length 直接用于 peek_bytes,但未像 UART 分支那样限制到内部缓冲能力;超大长度输入会把错误处理路径推迟到更深处,增加边界风险。

🔧 建议修改
     data_length = header.get<I2cHeader::DataLength>();
     consume_peeked();
 }
 
 if (slave_address > 0x7F) [[unlikely]]
     co_return false;
+
+if (data_length > sizeof(pending_bytes_buffer_)) [[unlikely]]
+    co_return false;

Also applies to: 329-334, 349-360

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/protocol/deserializer.cpp` at line 307, I2C 分支没有对 data_length(从
header.get<I2cHeader::DataLength>() 读取)做上界校验就传给
peek_bytes,可能导致超大长度把错误处理推迟并引发边界风险;请在使用 peek_bytes 或后续缓冲访问前参考 UART 分支的做法,对
data_length 做上界限制(例如 clamp 到内部缓冲容量或最大允许帧长),并在长度异常时直接返回/报错;修改点包含使用
header.get<I2cHeader::DataLength>() 的位置、传入 peek_bytes
的调用处以及与之对应的错误路径(同样修复类似位置的另两处出现处)。

Comment on lines +363 to +369
case I2cHeader::PayloadEnum::kError: {
data::I2cDataView data_view{};
data_view.slave_address = slave_address;
data_view.payload = std::span<const std::byte>{};
data_view.has_register = false;
callback_.i2c_error_deserialized_callback(field_id, data_view);
break;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

kError 分支未消费额外字节会导致流错位。

当报文头里 has_registerdata_length 非零时,当前分支直接回调并返回成功,剩余字节会被当成下一帧头解析,导致后续解析链路错位。

🔧 建议修改
 case I2cHeader::PayloadEnum::kError: {
+    if (has_register || data_length != 0) [[unlikely]]
+        co_return false;
     data::I2cDataView data_view{};
     data_view.slave_address = slave_address;
     data_view.payload = std::span<const std::byte>{};
     data_view.has_register = false;
     callback_.i2c_error_deserialized_callback(field_id, data_view);
     break;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case I2cHeader::PayloadEnum::kError: {
data::I2cDataView data_view{};
data_view.slave_address = slave_address;
data_view.payload = std::span<const std::byte>{};
data_view.has_register = false;
callback_.i2c_error_deserialized_callback(field_id, data_view);
break;
case I2cHeader::PayloadEnum::kError: {
if (has_register || data_length != 0) [[unlikely]]
co_return false;
data::I2cDataView data_view{};
data_view.slave_address = slave_address;
data_view.payload = std::span<const std::byte>{};
data_view.has_register = false;
callback_.i2c_error_deserialized_callback(field_id, data_view);
break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/protocol/deserializer.cpp` around lines 363 - 369, The kError branch
in I2cHeader::PayloadEnum::kError currently returns without consuming optional
register or payload bytes, causing stream desynchronization; update the branch
in deserializer.cpp to inspect the header's has_register and data_length fields
and consume/skip the corresponding bytes from the input buffer/stream (e.g.,
read and discard the register byte if has_register is true and advance by
data_length bytes for payload) before constructing data::I2cDataView (with empty
payload) and invoking callback_.i2c_error_deserialized_callback(field_id,
data_view); if you already have a helper like consumeBytes/advanceCursor or
existing read routines, reuse them to avoid duplicating parsing logic.

Comment on lines +297 to +312
uint8_t slave_address;
{
const utility::InterruptLockGuard guard;

if (transfer_state_.load(std::memory_order::relaxed) == TransferState::kIdle)
return;

if (!timer::timer->check_expired(transfer_start_timepoint_, kTransferTimeout))
return;

slave_address = active_request_.slave_address;
abort_dma_if_enabled(hal_i2c_handle_->hdmarx);
abort_dma_if_enabled(hal_i2c_handle_->hdmatx);
core::utility::assert_always(HAL_I2C_DeInit(hal_i2c_handle_) == HAL_OK);
core::utility::assert_always(HAL_I2C_Init(hal_i2c_handle_) == HAL_OK);
finish_active_request_locked();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

❓ Verification inconclusive

🏁 Script executed:

#!/bin/bash
set -e

echo '--- interrupt_lock.hpp ---'
fd -i 'interrupt_lock.hpp$' | xargs -r sed -n '1,200p'

echo
echo '--- HAL_DMA_Abort ---'
fd -i 'stm32f4xx_hal_dma.c$' | xargs -r rg -n -A30 -B5 '\bHAL_DMA_Abort\s*\('

echo
echo '--- HAL_I2C_Init / HAL_I2C_DeInit ---'
fd -i 'stm32f4xx_hal_i2c.c$' | xargs -r rg -n -A40 -B5 '\bHAL_I2C_(Init|DeInit)\s*\('

Repository: Alliance-Algorithm/librmcs

Length of output: 1818


超时恢复路径中 HAL 调用与全局关中断的交互风险

Lines 299-312(及 319-326)中,InterruptLockGuard 作用域内调用 HAL_DMA_Abort()HAL_I2C_DeInit()HAL_I2C_Init() 存在潜在风险。若该 guard 屏蔽全局中断(包括 SysTick),而这些 HAL 函数依赖轮询或超时机制,可能导致该恢复路径在超时条件下卡死。

建议验证:

  • InterruptLockGuard 的具体实现及其屏蔽范围
  • 相关 HAL 函数的超时轮询依赖关系

如确认存在此风险,应改为"临界区内仅摘取状态,HAL 调用移至临界区外"。

Comment on lines +148 to +160
void i2c_read_result_deserialized_callback(
core::protocol::FieldId id, const data::I2cDataView& data) override {
(void)id;
(void)data;
core::utility::assert_failed_always();
}

void i2c_error_deserialized_callback(
core::protocol::FieldId id, const data::I2cDataView& data) override {
(void)id;
(void)data;
core::utility::assert_failed_always();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

避免把可恢复协议异常升级为硬故障。

i2c_read_resulti2c_error 当前无条件 assert_failed_always(),异常输入会直接导致固件停机,建议改为丢弃/记录错误。

🔧 建议修改
 void i2c_read_result_deserialized_callback(
     core::protocol::FieldId id, const data::I2cDataView& data) override {
     (void)id;
     (void)data;
-    core::utility::assert_failed_always();
+    return;
 }
 
 void i2c_error_deserialized_callback(
     core::protocol::FieldId id, const data::I2cDataView& data) override {
     (void)id;
     (void)data;
-    core::utility::assert_failed_always();
+    return;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/c_board/app/src/usb/vendor.hpp` around lines 148 - 160, 这两个回调
i2c_read_result_deserialized_callback 和 i2c_error_deserialized_callback
不应在接收到协议级错误时调用 core::utility::assert_failed_always()
导致硬故障;请移除或替换断言,在处理不可恢复数据时改为记录/丢弃输入并安全返回(例如调用适当的日志函数或统计错误计数器,并直接
return),确保不会中断整个固件流程;保留函数签名和任何必需的 no-op 行为以满足接口契约。

Comment on lines +33 to +35
if (data.payload.empty())
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

空写入长度被静默忽略,应按非法长度上报 I2C 错误。

这里直接返回会让上层误判为“已处理”。与本 PR“非法长度上报错误”的目标不一致。

🐛 建议修改
     void handle_downlink_write(const data::I2cDataView& data) {
-        if (data.payload.empty())
+        if (data.payload.empty()) {
+            write_error(data.slave_address);
             return;
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/rmcs_board/app/src/i2c/i2c.hpp` around lines 33 - 35, 当前实现对
data.payload.empty() 直接 return 导致空写入被静默忽略,应改为上报非法长度错误并让调用方感知失败;在处理写请求的函数(检查
data.payload.empty() 的位置)移除裸 return,改为通过项目现有的 I2C
错误上报/返回机制上报一个“非法长度”错误(例如调用错误上报函数或返回错误码/false、或设置 I2C 错误状态),确保 payload.empty()
情况不会被当作成功处理。

Comment on lines +41 to +51
const auto* payload = reinterpret_cast<const uint8_t*>(data.payload.data());
hpm_stat_t status = status_fail;
if (data.has_register) {
uint8_t reg_address = data.reg_address;
status = i2c_master_address_write(
i2c_base_, data.slave_address, &reg_address, sizeof(reg_address),
const_cast<uint8_t*>(payload), static_cast<uint32_t>(data.payload.size()));
} else {
status = i2c_master_write(
i2c_base_, data.slave_address, const_cast<uint8_t*>(payload),
static_cast<uint32_t>(data.payload.size()));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate hpm_i2c_drv.h =="
fd -i 'hpm_i2c_drv.h'

HEADER="$(fd -i 'hpm_i2c_drv.h' | head -n 1)"
echo "== inspect API signatures in ${HEADER} =="
rg -n -C3 'i2c_master_(address_)?write\s*\(' "$HEADER"
rg -n -C3 'i2c_master_(address_)?read\s*\(' "$HEADER"

echo "== inspect driver implementation for tx-buffer writes =="
fd -i 'hpm_i2c*.c' | xargs -r rg -n -C2 'i2c_master_(address_)?write\s*\(|\*\s*[a-zA-Z_][a-zA-Z0-9_]*\s*=|\[[^]]+\]\s*='

Repository: Alliance-Algorithm/librmcs

Length of output: 1818


🌐 Web query:

HPM I2C API i2c_master_write i2c_master_address_write const buffer semantics

💡 Result:

In the HPMicro (HPM) SDK I2C driver API (hpm_i2c_drv.h), the functions i2c_master_write and i2c_master_address_write are blocking polling-mode APIs for I2C master transmit operations. i2c_master_write: Performs a standard I2C write to a slave device (device_address), sending 'size' bytes from 'buf'. Prototype: hpm_stat_t i2c_master_write(I2C_Type *ptr, const uint16_t device_address, uint8_t *buf, const uint32_t size). The 'buf' parameter is non-const (uint8_t *), indicating the driver may modify buffer contents (e.g., for status or NACK handling), despite being transmit-only. i2c_master_address_write: Performs a register write: sends device_address, then 'addr_size_in_byte' bytes from 'addr', then 'size_in_byte' bytes from 'buf'. Prototype: hpm_stat_t i2c_master_address_write(I2C_Type *ptr, const uint16_t device_address, uint8_t *addr, uint32_t addr_size_in_byte, uint8_t *buf, const uint32_t size_in_byte). Both 'addr' and 'buf' are non-const (uint8_t ), same semantics as above—the driver does not promise not to modify them. Note: Higher-level component APIs (hpm_i2c_) use uint8_t *buf (non-const). No evidence of const qualifiers in low-level driver prototypes; buffers should not be const-qualified data to avoid undefined behavior if modified.

Citations:


🌐 Web query:

HPM SDK RISC-V I2C driver API reference hpm_i2c_drv.h

💡 Result:

The hpm_i2c_drv.h file is the header for the I2C driver API in HPMicro's HPM SDK, used with their RISC-V based MCUs. It provides low-level driver functions for I2C peripherals. Key features from documentation: - Supports standard (100 kbps), fast (400 kbps), and fast-plus (1 Mbps) modes. - Supports 7-bit and 10-bit addressing. - Master and slave modes. - Interrupt and DMA support (tx/rx complete, byte received/sent, start/stop, arbitration lost, address hit, FIFO events). - 4-byte hardware FIFO (size defined in hpm_soc_feature.h as I2C_SOC_FIFO_SIZE). - Bus deadlock recovery (if HPM_IP_FEATURE_I2C_SUPPORT_RESET defined). Main APIs (from driver samples and component docs): - hpm_stat_t i2c_init_master(I2C_Type *ptr, uint32_t src_clk_in_hz, i2c_config_t *config); - hpm_stat_t i2c_master_write(I2C_Type *ptr, uint16_t device_address, uint8_t *buf, uint32_t size); (polling) - hpm_stat_t i2c_master_read(I2C_Type *ptr, uint16_t device_address, uint8_t *buf, uint32_t size); (polling) - hpm_stat_t i2c_enable_irq(I2C_Type *ptr, uint32_t mask); - uint32_t i2c_get_status(I2C_Type *ptr); for IRQ handling. - DMA APIs available via i2c_components layer. Source code and full API reference: - View source: https://hpm-sdk.readthedocs.io/en/latest/api_doc/hpmi2cdrv_8h_source.html - Directory listing confirms presence: https://hpm-sdk.readthedocs.io/en/latest/api_doc/dir_445f1c1c73deccb146d16af27ad5c1d1.html (under drivers/inc/) - SDK repo: https://github.com/hpmicro/hpm_sdk (latest v1.11.0 as of 2025-12-31) - Higher-level component: https://hpm-sdk.readthedocs.io/en/v1.10.0/components/i2c/README_en.html (hpm_i2c_context_t, blocking/non-blocking tx/rx) - Driver sample docs (Chinese): https://hpm-sdk.readthedocs.io/zh-cn/v1.10.0/samples/drivers/i2c/index_zh.html Official site: https://www.hpmicro.com (HPMicro, RISC-V MCU vendor). Download SDK from GitHub releases or https://resource.hpmicro.com/sdk_env_v1.11.0.zip. Build docs locally with Doxygen for full API.

Citations:


HPM I2C 驱动 API 无法接受 const 缓冲区,使用 const_cast 产生未定义行为。

根据 HPM SDK 官方文档,i2c_master_write()i2c_master_address_write() 的缓冲区参数为非 const uint8_t*,驱动可能在 NACK 或状态处理过程中修改缓冲区内容。当前代码将只读的 data.payload 通过 const_cast 转为可写指针传入,违反 API 契约,构成未定义行为。

建议:将数据复制至可写的临时缓冲区后再调用驱动接口,或在 data.payload 允许的情况下改为非 const 存储。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/rmcs_board/app/src/i2c/i2c.hpp` around lines 41 - 51, The code
currently const_casts data.payload into a mutable pointer and passes it to HPM
APIs (i2c_master_write and i2c_master_address_write), which can mutate the
buffer; instead allocate a local mutable buffer, copy data.payload into it, and
pass that mutable buffer to i2c_master_write/i2c_master_address_write (use
payload/local_buffer and ensure size uses data.payload.size()); update both
branches (the reg_address branch using i2c_master_address_write and the plain
write branch using i2c_master_write) to use the temporary writable buffer and
free or let it go out of scope after the call.

Comment on lines +158 to +170
void i2c_read_result_deserialized_callback(
core::protocol::FieldId id, const data::I2cDataView& data) override {
(void)id;
(void)data;
core::utility::assert_failed_always();
}

void i2c_error_deserialized_callback(
core::protocol::FieldId id, const data::I2cDataView& data) override {
(void)id;
(void)data;
core::utility::assert_failed_always();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

不要对“方向错误但可解析”的 I2C 包直接触发硬断言。

当前对 i2c_read_result/i2c_error 的无条件 trap 会把协议输入异常变成设备不可用,建议改为丢弃并上报软错误(或至少 debug 断言)。

🔧 建议修改
 void i2c_read_result_deserialized_callback(
     core::protocol::FieldId id, const data::I2cDataView& data) override {
     (void)id;
     (void)data;
-    core::utility::assert_failed_always();
+    // Unexpected in downlink direction on rmcs_board; drop to keep device alive.
+    return;
 }
 
 void i2c_error_deserialized_callback(
     core::protocol::FieldId id, const data::I2cDataView& data) override {
     (void)id;
     (void)data;
-    core::utility::assert_failed_always();
+    // Unexpected in downlink direction on rmcs_board; drop to keep device alive.
+    return;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/rmcs_board/app/src/usb/vendor.hpp` around lines 158 - 170, Replace
the unconditional hard assert in i2c_read_result_deserialized_callback and
i2c_error_deserialized_callback with non-fatal handling: detect that the packet
direction is incorrect, drop the message, emit a soft error/reporting call
(e.g., increment an error counter, call a logger or
core::diagnostics::report_error) and optionally use a debug-only assert or log
for development builds; ensure both functions stop calling
core::utility::assert_failed_always() and instead record the error and return
gracefully so the device remains usable.

@gqsdjhh gqsdjhh closed this Apr 12, 2026
@github-project-automation github-project-automation bot moved this from Todo to Done in RMCS Slave SDK Apr 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant